从非法指令到解码路径:RISC-V 编码空间结构化的实践
Transformed from Logseq page
从非法指令到解码路径:RISC-V 编码空间结构化的实践
-
public:: true
-
调试 CPU 或内核时,最令人恼火的错误之一,是反汇编器只丢给你一句
illegal instruction。
没有上下文,没有线索——你不知道这条非法编码离哪条合法指令最近,不知道它落入了哪个编码子空间,更不知道它的各个字段(opcode / funct3 / funct6 / imm 等)如果按某种格式解读会是什么样子。这让我开始思考:我们能不能让解码器在遇到非法指令时,不只说“非法”,而是说“在哪个空间里非法”?
-
现状:扁平的表,分层的空间#
RISC-V 的编码空间本身是有层次的:
-
30-bit 基础空间(bit 31–2)
-
25-bit 主要操作码(Major Opcode)空间
-
22-bit 次要操作码(funct3)空间
-
进一步由 funct7 / funct6 / imm 等字段细分
-
-
这个结构天然对应一棵解码决策树。然而,当前流行的机器可读数据源,并没有直接描述这棵树。
-
riscv-opcodes:提供的是(mask, match)对,本质是一张扁平表。解码器只能回答“这个编码匹配哪条指令”,无法回答“这个编码在哪个子空间”。 -
RISC-V Unified Database (UDB):它引入
type-subtype继承机制,初衷是复用指令格式定义(比如 R-type 的字段位置)。但它无法表达“部分匹配”的节点——例如“opcode=0b0010011, funct3=0b001, funct7 是 don’t-care”这样的中间子空间。继承是 deep-merge + 覆盖,子级必须填所有字段,不能只填部分、留其余为 don’t-care。-
我仔细分析了 UDB 的继承实现(
yaml_resolver.rb中的 deep-merge 逻辑),结论是:type-subtype 解决的是“指令的形状”,而编码空间层次是“解码树的形状”,两者正交,不能互相推导。
-
-
-
转折:gem5 的 decoder.isa 是一棵现成的树#
-
正当我苦恼于没有结构化数据时,注意到了 gem5 模拟器中的
src/arch/riscv/isa/decoder.isa。
它用 gem5 的领域特定语言(DSL)手写了完整的解码决策树:
plaintextdecode QUADRANT { 0x0: decode COMPRESSED { ... } 0x3: decode OPCODE5 { ... } }这棵树不仅包含了所有指令的精确匹配,更重要的是它显式地编码了逐级细分的逻辑——每一步检查哪个字段、根据什么值分支、子空间里有哪些合法值。
这正是我们需要的“解码路径”信息。于是,我们决定从 gem5 的 parser 中提取这棵树,转换成 JSON,然后编写一个独立的解码脚本,让它能“走”这棵树并报告每一步。
-
实现:从 gem5 到独立 skill#
-
1. 修改 gem5 的 ISA parser#
-
gem5 的
isa_parser.py在解析decoder.isa时,已经构建了 AST,但只用于生成 C++ 代码。
我们给 parser 增加了:-
DecodeTreeNode类(支持递归序列化为 dict) -
在
p_decode_block、p_decode_stmt_decode、p_inst_0/1等 production 中挂接,生成block、case、instruction三种节点 -
添加
subset标签收集(从指令参数中识别OPIVV、OPFVV、OPIVI等向量子集) -
最终输出
decode_tree.json
-
-
这个 patch 只有 200 多行,不影响 gem5 原有的 C++ 生成,仅额外导出 JSON。
-
加载
decode_tree.json -
对每条 32-bit 指令,从根节点开始遍历:
-
若当前节点是
block,提取指定字段的值,在case列表中查找匹配分支 -
若匹配成功,记录字段值和位范围,进入下一节点
-
若匹配失败,记录失败字段,并尝试从兄弟指令推导期望的格式(用于非法指令的 operand 展示)
-
-
最后输出:
-
每一步的字段名、值、位范围、子集标签
-
完整 32-bit 二进制按字段划分的紧凑表示(含 operand 字段)
-
-
3. 格式与 operand 的映射#
最初我们硬编码了
VS2/VS1/VD作为 operand 字段,但这是错误的——不同指令格式的 operand 不同。
因此我们建立了FMT_OPERANDS字典,覆盖了 gem5 中约 70 种格式,为每种格式指定其 operand 字段(例如IOp→rd, rs1, imm12;VectorIntFormat→vd, vs2, vs1)。
这样,最终二进制展开就能正确显示与指令格式匹配的 operand。 -
效果展示#
-
以两个非法指令为例:
-
例1:
vmv<nr>r的SIMM3字段非法#指令
0x9e053057在 NEMU 中曾引发异常。我们的工具输出:
plaintext0x9e053057 -> QUADRANT=0x3 [1:0] -> OPCODE5=0x15 [6:2] [OPFVF/OPFVV/OPIVI/OPIVV/OPIVX/OPMVV/OPMVX/VConfOp] -> FUNCT3=0x3 [14:12] [OPIVI] -> VFUNCT6=0x27 [31:26] [OPIVI] -> VM=0x1 [25:25] [OPIVI] -> SIMM3=0x2 ✗ not in ['0x0', '0x1', '0x3', '0x7'] = 100111<VFUNCT6> 1<VM> 00000<vs2> 010<SIMM3 ✗> 011<FUNCT3> 00000<vd> 10101<OPCODE5> 11<QUADRANT> ILLEGAL INSTRUCTION SIMM3=0x2 not in ['0x0', '0x1', '0x3', '0x7'] hint: SIMM3 encodes NREG-1; legal: 0,1,3,7 (NREG=1,2,4,8)它明确告诉我们:指令匹配到了
OPIVI子空间,进入了vmv<nr>r的候选范围,但SIMM3字段的值0x2不在允许集合中。 -
例2:
vlm与width的非法组合#0x02b56487是另一个 NEMU bug 案例:LUMOP=0xb(vlm)却搭配了width=110(VLE32),而 vlm 要求width=000。我们的工具输出:
plaintext0x02b56487 -> QUADRANT=0x3 [1:0] -> OPCODE5=0x1 [6:2] [Load/VlIndexOp/VlSegOp/VlStrideOp/VlWholeOp/VleOp/VlmOp] -> FUNCT3=0x6 [14:12] [VlIndexOp/VlSegOp/VlStrideOp/VlWholeOp/VleOp] -> MOP=0x0 [27:26] [VlSegOp/VlWholeOp/VleOp] -> LUMOP=0xb ✗ not in ['0x0', '0x8', '0x10'] = 00<MOP> 01011<LUMOP ✗> 01010<rs1> 110<FUNCT3> 01001<vd> 00001<OPCODE5> 11<QUADRANT> ILLEGAL INSTRUCTION LUMOP=0xb not in ['0x0', '0x8', '0x10']这里我们看到,解码树在
FUNCT3=0x6分支下,LUMOP的合法值只有0x0(常规单元步长)、0x8(整寄存器)、0x10(fault-only-first),而0xb被拒。
同时,二进制展开中rs1和vd字段也被正确识别(尽管它们不是解码路径的一部分),帮助用户直观理解该编码的构成。 -
大多数解码器(
objdump、riscv-isa、tinyrv等)都是精确查表器,对未知编码只报“invalid”或输出原始字节。-
没有工具能“汇报决策树路径”或“猜测最相似格式”。
-
社区中也没有现成的 skill 或插件能一键完成这种分析。
-
-
这是为什么?我觉得有几个可能的原因:
-
- 需求不常见:大多数开发者只关心合法指令的汇编,非法指令通常被视为“不该出现”的异常,一旦出现就归咎于工具链或编译器,不需要精细分析。
- 手工数 bit 的惯性:在调试底层问题时,很多开发者仍然习惯于手动拆解二进制(“数 bit”),虽然慢但直接,没有意识到可以自动化。
- 工具链门槛:要构建这样的工具,需要深入 gem5 的 ISA parser 或 UDB 的继承机制,这对普通开发者来说并不友好,导致没有形成通用的解决方案。
- RISC-V 的碎片化:指令集扩展繁多,不同配置下的编码空间差异大,一个静态的“最相似”算法难以覆盖所有组合,进一步阻碍了通用工具的出现。
-
-
但我认为,随着 RISC-V 在 CPU 设计、内核开发、硬件加速等领域越来越深入,这种“能告诉你为什么非法”的工具会变得更有价值。它不仅帮助调试,也能用于教学(展示编码空间的层次结构),以及自定义扩展开发(帮助寻找可用的编码空间)。
-
总结#
本文从一次非法指令调试的痛点出发,梳理了 RISC-V 编码空间的结构化问题,指出当前 flat 表数据源的不足,并借助 gem5 的
decoder.isa实现了解码路径追踪和非法指令的诊断信息输出。
我们将其封装为一个独立的 skill(riscv-inst-decoder),并提供 gem5 parser 的 patch,以便社区复现和扩展。如果你也曾在面对
illegal instruction时感到茫然,希望这个工具能帮你少数几个 bit,多几分从容。 -
相关资源:
-
gem5 patch:
riscv-isa-skills/patches/gem5_decode_tree.patch -
解析后的
decode_tree.json已包含在 skill 中,无需 gem5 运行时即可使用。